﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Data;
using System.Text;
using System.Diagnostics;

namespace MyClever.Lib
{
    public class MissionPluginLoopNode : MissionPluginNode
    {        
        private List<MissionPluginNode> pluginNodes = new List<MissionPluginNode>();

        // Todo: we must refactor all the moveNext stuff
        private int currentPluginIndex;

        private PackageReference packageReference;

        private DataTableReader loopPackageReader;

        protected static int numPluginLoopNodes;

        public override int ID
        {
            get 
            {
                return this.id;
            }
        }

        public List<MissionPluginNode> PluginNodes
        {
            get { return this.pluginNodes; }
        }

        public PackageReference PackageReference
        {
            get { return this.packageReference; }
        }

        // is this a real loop?
        // ATTENTION: the very first MissionPluginProxyNode (id = 0) is most of the time a virtual loop! virtual means it has NO loop-definition
        public bool IsVirtualLoop
        {
            get { return (null == this.Parent && null == this.packageReference); }
        }

        public override MissionPluginLoopNode Parent
        {
            get { return this.parentNode; }
            set { this.parentNode = value; }
        }

        public MissionPluginLoopNode(int loopID, PackageReference packageReference)
        {
            this.id = loopID;
            this.packageReference = packageReference;

            this.currentPluginIndex = 0;
            this.loopPackageReader = null;
            this.parentNode = null;
        }

        private MissionPluginLoopNode GetRoot()
        {
            // start with this as root
            MissionPluginLoopNode root = this;
            // get the parent
            MissionPluginLoopNode parent = this.Parent;

            // keep going until no more parents
            while (null != parent)
            {
                // save the parent
                root = parent;
                // get the parent of the parent
                parent = parent.Parent;
            }
            return root;
        }

        private void CalcPluginPosition(RelativePluginPosition relativePluginPosition, ref MissionPluginLoopNode loopNode, out int nodeIndex)
        {
            nodeIndex = -1;

            for (int i = 0; i < this.pluginNodes.Count; i++)
            {
                if (typeof(MissionPluginLoopNode) == this.pluginNodes[i].GetType())
                {
                    loopNode = (MissionPluginLoopNode)this.pluginNodes[i];
                    loopNode.CalcPluginPosition(relativePluginPosition, ref loopNode, out nodeIndex);
                }
                else 
                {
                    if (relativePluginPosition.basedOnPluginID == loopNode.pluginNodes[i].ID)
                    {
                        loopNode = this;
                        nodeIndex = i;

                        if (DockOrientation.Right == relativePluginPosition.orientation)
                        {
                            nodeIndex = i + 1;
                        }
                        break;
                    }
                }
            }
        }

        public override void Add(MissionPluginNode pluginNode, RelativePluginPosition relativePluginPosition)
        {
            MissionPluginLoopNode pluginLoop = this;
            int nodeIndex;

            // the very fist plugin has no relative plugin position
            if (null == relativePluginPosition)
            {
                nodeIndex = 0;
                pluginLoop.pluginNodes.Add(pluginNode);
            }
            else
            {
                this.CalcPluginPosition(relativePluginPosition, ref pluginLoop, out nodeIndex);

                if (DockOrientation.Down == relativePluginPosition.orientation)
                {
                    MissionPluginLoopNode newLoopNode = new MissionPluginLoopNode((++MissionPluginLoopNode.numPluginLoopNodes), null);
                    newLoopNode.parentNode = this;
                    newLoopNode.pluginNodes.Add(pluginLoop.pluginNodes[nodeIndex]);
                    newLoopNode.pluginNodes.Add(pluginNode);

                    pluginLoop.pluginNodes.RemoveAt(nodeIndex);
                    pluginLoop.pluginNodes.Insert(nodeIndex, newLoopNode);
                }
                else
                {
                    pluginLoop.pluginNodes.Insert(nodeIndex, pluginNode);
                }
            }
        }
        /// <summary>
        /// This is a shortcut function for Add with DockOrientation Right
        /// </summary>
        /// <param name="pluginNode">The element you like to add.</param>
        public override void Append(MissionPluginNode pluginNode)
        {
            pluginNode.Parent = this;            
            this.pluginNodes.Add(pluginNode);
        }

        //public override void Remove(MissionPluginNode component)
        //{
        //    this.pluginNodes.Remove(component);
        //} 

        //public override void Display(int depth)
        //{
        //    Console.WriteLine(new String('-', depth) + name);

        //    // Recursively display child nodes
        //    foreach (Component component in _children)
        //    {
        //        component.Display(depth + 2);
        //    }
        //}



        public override void GetPlugins(ref List<MissionPluginProxy> plugins)
        {
            foreach (MissionPluginNode pluginNodeOn in this.pluginNodes)
            {
                pluginNodeOn.GetPlugins(ref plugins);
            }
        }

        public MissionPluginProxy GetPluginByID(int pluginID)
        {
            List<MissionPluginProxy> plugins = new List<MissionPluginProxy>();
            this.GetPlugins(ref plugins);

            foreach (MissionPluginProxy pluginOn in plugins)
            {
                if (pluginID == pluginOn.ID)
                {
                    return pluginOn;
                }
            }
            return null;
        }

        public MissionPluginLoopNode GetPluginLoopNodeByID(int pluginNodeID)
        {
            if (pluginNodeID == this.ID)
            {
                return this;
            }
            
            foreach (MissionPluginNode pluginNodeOn in this.pluginNodes)
            {
                if (typeof(MissionPluginLoopNode) == pluginNodeOn.GetType())
                {
                    MissionPluginLoopNode found = ((MissionPluginLoopNode)pluginNodeOn).GetPluginLoopNodeByID(pluginNodeID);
                    if (null != found)
                    {
                        return found;
                    }
                }
	        }
            return null;
        }

        /// <summary>
        /// Returns a list of plugin ids, which have execute at least one error message.
        /// </summary>
        /// <returns></returns>
        public List<MissionPluginProxy> GetDefectivePlugins()
        {
            // todo: sind alle logging-Packeges mit 100% abgeschlossen?

            List<MissionPluginProxy> plugins = new List<MissionPluginProxy>();
            this.GetPlugins(ref plugins);

            List<MissionPluginProxy> defectivePluginIDs = new List<MissionPluginProxy>();
            foreach (MissionPluginProxy pluginOn in plugins)
            {
                if (true == pluginOn.HasErrorMessages())
                {
                    defectivePluginIDs.Add(pluginOn);
                }
            }
            return defectivePluginIDs;
        }

        public MissionPluginProxy MoveNext()
        {
            // while the next element is a loop-definition itself
            while (typeof(MissionPluginLoopNode) == this.pluginNodes[this.currentPluginIndex % this.pluginNodes.Count].GetType())
            {
                // start the new internal loop? (start recursion!)
                MissionPluginLoopNode nextLoop = (MissionPluginLoopNode)this.pluginNodes[this.currentPluginIndex % this.pluginNodes.Count];
                MissionPluginProxy nextPlugin = nextLoop.MoveNext();
                if (null != nextPlugin)
                {
                    // until we can return the next valid plugin to start
                    return nextPlugin;
                }
                else
                {
                    this.currentPluginIndex++;
                }
            }

            // if we we START looping ...
            if (this.currentPluginIndex % this.pluginNodes.Count == 0)
            {
                if (false == this.IsVirtualLoop)
                {
                    // we must initialize the loopDataReader, if necessary
                    if (null == this.loopPackageReader)
                    {
                        //the loopDataReader holds the data, the loop runs through
                        MissionPluginPackage linkedPackage;
                        MissionPluginPackageColumn linkedPackageColumn;
                        this.GetReferenceInstance(this.packageReference, out linkedPackage, out linkedPackageColumn);

                        MissionPluginPackage loopPackage = (MissionPluginPackage)linkedPackage.Clone();
                        loopPackage.CopyDataFromPackage(linkedPackage);
                        this.loopPackageReader = loopPackage.CreateDataReader();
                    }

                    // but if the loopDataReader is created (before)
                    if (null != this.loopPackageReader)
                    {
                        // we must read a new row from the loop data
                        bool wasReadingSuccessful = this.loopPackageReader.Read();

                        //until the whole data were read
                        if (false == wasReadingSuccessful)
                        {
                            this.loopPackageReader = null;
                            return null;
                        }
                    }

                }
                else
                {
                    if (this.currentPluginIndex >= this.pluginNodes.Count)
                    {
                        return null;
                    }
                }
            }

            MissionPluginProxy plugin = (MissionPluginProxy)this.pluginNodes[this.currentPluginIndex % this.pluginNodes.Count];
            plugin.Reset();

            // refresh linked packages
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                if (true == packageOn.HasReference)
                {
                    MissionPluginPackage linkedPackage;
                    MissionPluginPackageColumn linkedColumn;
                    this.GetReferenceInstance(packageOn.Reference, out linkedPackage, out linkedColumn);

                    packageOn.CopyDataFromPackage(linkedPackage);
                }
            }

            // refresh linked package-columns
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                // we must get every row of this column                    
                List<MissionPluginPackageColumn> referencedColumns = packageOn.GetColumnsWithColumnReference();
                if (referencedColumns.Count > 0)
                {
                    int currentRow = 0;
                    do
                    {
                        DataRow newRow = packageOn.NewRow();

                        foreach (MissionPluginPackageColumn columnOn in referencedColumns)
                        {
                            MissionPluginPackage linkedPackage;
                            MissionPluginPackageColumn linkedColumn;
                            this.GetReferenceInstance(columnOn.ColumnReference, out linkedPackage, out linkedColumn);

                            if (currentRow >= 0 && currentRow < linkedColumn.Table.Rows.Count)
                            {
                                newRow[columnOn.ColumnName] = linkedColumn.Table.Rows[currentRow][linkedColumn.ColumnName];
                            }
                            else
                            {
                                currentRow = -1;
                            }
                        }
                        currentRow++;
                        if (currentRow > 0)
                        {
                            packageOn.Rows.Add(newRow);
                        }
                    } while (currentRow > 0);
                }
            }

            // refresh all cell references
            foreach (MissionPluginPackage packageOn in plugin.GetPackages(Direction.Input))
            {
                foreach (MissionPluginPackageColumn columnOn in packageOn.GetColumnsWithCellReference())
                {
                    DataRow firstRow = packageOn.Rows[0];
                    if (true == firstRow.IsNull(columnOn))
                    {
                        MissionPluginLoopNode referencedLoop = this.GetRoot().GetPluginLoopNodeByID(Convert.ToInt32(columnOn.CellReference.LoopID));
                        firstRow[columnOn] = referencedLoop.loopPackageReader[(string)columnOn.CellReference.ColumnName.ToString()];
                    }
                }
            }

            this.currentPluginIndex++;
            return plugin;
        }

        private void GetReferenceInstance(PackageReference reference, out MissionPluginPackage packageInstance, out MissionPluginPackageColumn columnInstance)
        {           
            packageInstance = null;
            columnInstance = null;

            MissionPluginProxy linkedPlugin = this.GetRoot().GetPluginByID(reference.PluginID);
            packageInstance = linkedPlugin.GetPackageByName(reference.PackageName);
            if (typeof(PackageColumnReference) == reference.GetType())
            {
                columnInstance = packageInstance.GetColumnByName(((PackageColumnReference)reference).ColumnName);
            }
        }
    }
}
